package com.google.code.sms.service;

import java.io.File;
import java.util.Date;

import com.google.code.sms.Logger;
import com.google.code.sms.dao.MediaElementDao;
import com.google.code.sms.domain.MediaElement;
import com.google.code.sms.domain.MediaFolder;
import com.google.code.sms.util.FileUtil;

/**
 * Provides services for scanning media folders.
 *
 * @author Scott Ware
 */
public class MediaScannerService {

    private static final Logger LOG = Logger.getLogger("MediaScannerService");
    private boolean scanning;
    private SettingsService settingsService;
    private SearchService searchService;
    private MediaElementService mediaElementService;
    private MediaElementDao mediaElementDao;
    private int scanCount, mediaFolderCount, newFiles, newFolders;

    public void init() {
        deleteOldIndexFile();
    }

    /**
     * Returns whether the media library is currently being scanned.
     */
    public synchronized boolean isScanning() {
        return scanning;
    }

    /**
     * Returns the number of files scanned so far.
     */
    public int getScanCount() {
        return scanCount;
    }

    /**
     * Scans the media library.
     * The scanning is done asynchronously in it's own thread.
     */
    public synchronized void scanLibrary() {
        if (isScanning()) {
            return;
        }
        scanning = true;

        Thread thread = new Thread("MediaLibraryScanner") {
            @Override
            public void run() {
                doScanLibrary();
            }
        };

        thread.setPriority(Thread.MIN_PRIORITY);
        thread.start();
    }

    private void doScanLibrary() 
    {
        LOG.info("Starting to scan media library.");

        try {
            Date lastScanned = new Date();
            
            scanCount = 0;
            mediaFolderCount = 0;
            newFiles = 0;
            newFolders = 0;

            searchService.startIndexing();

            // Recurse through all files and folders on disk for each MediaFolder
            for (MediaFolder mediaFolder : settingsService.getAllMediaFolders()) 
            {
                mediaFolderCount++;
                scanDirectory(mediaFolder.getFile(), lastScanned);
            }
         
            LOG.info("Scanned " + scanCount + " items in " + mediaFolderCount + " Media Folders.");
            LOG.info("Added " + newFiles + " files and " + newFolders + " folders to database.");

            LOG.info("Marking missing files and folders in database.");
            mediaElementDao.markNotPresent(lastScanned);

            settingsService.setLastScanned(lastScanned);
            settingsService.save(false);
            LOG.info("Completed media library scan.");

        } catch (Throwable x) {
            LOG.error("Failed to scan media library.", x);
        } finally {
            scanning = false;
            searchService.stopIndexing();
        }
    }

    void scanDirectory(File directory, Date lastScanned)
    {
        if(directory == null)
            return;
        
        // Recurse through the directory
        for(File currentItem : directory.listFiles())
        {
            // Increment scan count
            scanCount++;
            
            // If the current item is a directory, process it and scan it's contents
            if(currentItem.isDirectory())
            {
                // Add directory to database
                if(mediaElementService.createOrUpdateMediaElement(currentItem, lastScanned))
                {
                    newFolders++;
                }
                
                // Add directory to search index
                MediaElement element = mediaElementService.getMediaElement(currentItem.getPath());
                
                if(element != null)
                {
                    searchService.index(element);
                }
                
                // Recursively scan directories
                scanDirectory(currentItem, lastScanned);
            }
            // If the current item is a non-hidden file, process it
            else if(!FileUtil.isHidden(currentItem))
            {
                // Add media file to database
                if(mediaElementService.createOrUpdateMediaElement(currentItem, lastScanned))
                {
                    newFiles++;
                }
                
                // Add media file to search index
                MediaElement element = mediaElementService.getMediaElement(currentItem.getPath());
                
                if(element != null)
                {
                    searchService.index(element);
                }
            }
        }
    }
    
    //
    // Search Index Utilities
    //
    
    /**
     * Delete old search index file.
     */
    private void deleteOldIndexFile() 
    {
        File file = getIndexFile();
        try {
            if (file.exists()) {
                if (file.delete()) {
                    LOG.info("Deleted old index file.");
                }
            }
        } catch (Exception x) {
            LOG.warn("Failed to delete old index file.", x);
        }
    }

    /**
     * Returns the search index file.
     *
     * @return The index file for the given index version.
     */
    private File getIndexFile() {
        File home = SettingsService.getSMSHome();
        return new File(home, "sms.index");
    }

    //
    // Services & Database Managers
    //
    
    public void setSettingsService(SettingsService settingsService) {
        this.settingsService = settingsService;
    }

    public void setSearchService(SearchService searchService) {
        this.searchService = searchService;
    }

    public void setMediaElementService(MediaElementService mediaElementService) {
        this.mediaElementService = mediaElementService;
    }

    public void setMediaElementDao(MediaElementDao mediaElementDao) {
        this.mediaElementDao = mediaElementDao;
    }
}
